package system

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"os"
	"path/filepath"
	"server/global"
	"server/model/common/response"
	CustomerModel "server/model/customer"
	systemReq "server/model/system/request"
	"server/pkg/S3"
	"server/utils"
	"sort"
	"strconv"
	"sync"
)

type UploadApi struct{}

func contains(slice []string, val string) bool {
	for _, item := range slice {
		if item == val {
			return true
		}
	}
	return false
}

// VerifyUpload 获取即将上传的文件元信息
func (ui *UploadApi) VerifyUpload(c *gin.Context) {
	var verifyFile systemReq.FileMetaInfo
	if err := c.ShouldBindJSON(&verifyFile); err != nil {
		response.FailWithMessage("Invalid request", c)
		return
	}

	targetDir := global.TD27_CONFIG.System.LocalStoreDir + "file/" // 设置上传文件的存储路径
	fileName := verifyFile.FileHash + "_" + verifyFile.FileName
	mergedFilePath := filepath.Join(targetDir, fileName)

	fileInfo, err := os.Stat(mergedFilePath)
	if err == nil && fileInfo.Size() > 0 {
		// 检查是否在S3上
		isOnS3, _ := S3.CheckFileExistsS3("file", fileName)
		if isOnS3 {
			md5Str := utils.GetFileMD5(mergedFilePath)
			if md5Str == verifyFile.FileHash {
				// 已存在完整文件，不需要上传
				verifyFile.ShouldUpload = false
				response.OkWithDetailed(verifyFile, "查询成功", c)
				return
			} else {
				os.Remove(mergedFilePath)
				S3.DeleteFileOnS3("file", fileName)
			}
		} else {
			msg := fmt.Sprintf("文件：%s 已存在本地，但S3上没有，需要重新上传.", verifyFile.FileName)
			global.TD27_LOG.Warn(msg)
		}
	}

	// 获取分片上传S3所需的uploadID
	var chunkUpload CustomerModel.ChunkUploadModel
	err = global.TD27_DB.Model(&CustomerModel.ChunkUploadModel{}).
		Select("upload_id").
		Where("file_hash = ?", verifyFile.FileHash).
		First(&chunkUpload).Error

	if err != nil {
		msg := fmt.Sprintf("查询数据库中hash值为 %s 的uploadID错误，error: %s", verifyFile.FileHash, err)
		global.TD27_LOG.Warn(msg)
	}

	if chunkUpload.UploadID != "" {
		verifyFile.UploadID = chunkUpload.UploadID
	} else {
		verifyFile.UploadID, err = S3.InitiateMultipartUpload("file", fileName)
		if err != nil {
			msg := fmt.Sprintf("获取分片上传S3所需的uploadID时遇到错误，error: %s", err)
			response.FailWithMessage(msg, c)
			return
		}
	}

	// 获取已上传的切片列表
	verifyFile.ShouldUpload = true
	UploadedList, _ := getUploadedList(verifyFile.FileHash, targetDir)
	// 查询S3已有的切片
	var partNumbers []int64
	// 使用数据库查询来获取所有的 PartNumber
	global.TD27_DB.Model(&CustomerModel.ChunkUploadModel{}).Select("part_number").Where("upload_id = ?", verifyFile.UploadID).Find(&partNumbers)

	// 将 PartNumber 转换为字符串形式
	partNumbersStr := make([]string, len(partNumbers))
	for i, num := range partNumbers {
		partNumbersStr[i] = fmt.Sprintf("%d", num)
	}

	// 通过交集操作筛选出共同存在的 PartNumber
	for _, num := range UploadedList {
		if contains(partNumbersStr, num) {
			verifyFile.UploadedList = append(verifyFile.UploadedList, num)
		}
	}

	if len(verifyFile.UploadedList) == 0 {
		verifyFile.UploadedList = []string{}
	}

	response.OkWithDetailed(verifyFile, "查询成功", c)
}

// getUploadedList 获取已上传的切片列表
func getUploadedList(fileHash string, targetDir string) (uploadedList []string, err error) {
	chunkDir := filepath.Join(targetDir, fileHash)
	_, err = os.Stat(chunkDir)
	if os.IsNotExist(err) {
		return []string{}, err
	}

	// 获取切片文件列表
	chunks, err := os.ReadDir(chunkDir)
	if err != nil {
		return []string{}, err
	}

	// 提取文件名并进行排序(文件名必须为int类型，否则无法正确的排序)
	var uint8Slice []uint8
	for _, chunk := range chunks {
		name, _ := utils.StringToUint8(chunk.Name())
		uint8Slice = append(uint8Slice, name)
	}
	// 对文件名进行排序
	sort.Slice(uint8Slice, func(i, j int) bool {
		return uint8Slice[i] < uint8Slice[j]
	})

	// 将排序后的结果中的元素转为字符串
	uploadedList = make([]string, len(uint8Slice))
	for i, val := range uint8Slice {
		uploadedList[i] = strconv.Itoa(int(val))
	}
	return
}

// UploadChunk 上传块文件
func (ui *UploadApi) UploadChunk(c *gin.Context) {
	var wg sync.WaitGroup
	// 解析上传的参数，如切片的哈希、文件名、文件哈希等。
	chunkNumber := c.Query("num")
	fileHash := c.Query("fileHash")
	s3UploadID := c.Query("s3UploadID")
	fileName := c.Query("fileName")
	if chunkNumber == "" || fileHash == "" || s3UploadID == "" || fileName == "" {
		msg := fmt.Sprintf("query参数：chunkNumber、fileHash、s3UploadID及fileName均为必填！")
		response.FailWithMessage(msg, c)
		return
	}

	chunkFile, err := c.FormFile("chunk")

	if err != nil {
		response.FailWithMessage("Failed to get chunk file", c)
		return
	}

	fileBytes, err := utils.FileToBytes(chunkFile)
	uploadsDir := global.TD27_CONFIG.System.LocalStoreDir + "file/" + fileHash + "/"
	filePath := uploadsDir + chunkNumber

	_, err = os.Stat(uploadsDir)
	if os.IsNotExist(err) {
		err = os.MkdirAll(uploadsDir, os.ModePerm)
		if err != nil {
			msg := fmt.Sprintf("创建目录 %s 失败，error: %s", uploadsDir, err)
			global.TD27_LOG.Error(msg)
			response.FailWithMessage(msg, c)
			return
		}
	}

	wg.Add(2)
	// 创建一个用于接收错误信息的通道
	errChan := make(chan string, 1)
	defer close(errChan) // 关闭通道

	// 保存到磁盘
	go func() {
		defer wg.Done()
		err = os.WriteFile(filePath, fileBytes, 0644)
		if err != nil {
			msg := fmt.Sprintf("保存文件 %s 失败，error: %s", chunkNumber, err)
			errChan <- msg // 发送错误信息到通道
			return
		}
		global.TD27_LOG.Info(fmt.Sprintf("文件: %s 保存至本地成功。", chunkNumber))
	}()

	// 上传至S3
	go func() {
		defer wg.Done()
		chunkNum, _ := utils.StringToInt64(chunkNumber)
		err = S3.UploadChunkToS3("file", fileHash, fileName, s3UploadID, chunkNum, fileBytes)
		if err != nil {
			msg := fmt.Sprintf("上传块文件 %s 到S3失败，error: %s", chunkNumber, err)
			errChan <- msg // 发送错误信息到通道
			return
		}
		msg := fmt.Sprintf("上传块文件 %s 到S3成功.", chunkNumber)
		global.TD27_LOG.Info(msg)
	}()

	// 等待两个操作都完成
	wg.Wait()
	// 检查是否有错误发生
	select {
	case err := <-errChan:
		// 处理错误信息并返回
		msg := fmt.Sprintf("上传失败，error: %s", err)
		global.TD27_LOG.Error(msg)
		response.FailWithMessage(msg, c)
		return
	default:
		response.OkWithMessage("Chunk uploaded successfully", c)
	}
}

// MergeChunks 合并块文件为完整文件后删除块文件
func (ui *UploadApi) MergeChunks(c *gin.Context) {
	var mergeChunk systemReq.MergeChunk
	if err := c.ShouldBindJSON(&mergeChunk); err != nil {
		msg := fmt.Sprintf("无效的请求！error: %s", err)
		response.FailWithMessage(msg, c)
		return
	}

	// 通知S3合并分片
	fileName := mergeChunk.FileHash + "_" + mergeChunk.FileName
	err := S3.CompleteMultipartUpload("file", fileName, mergeChunk.UploadID)
	if err != nil {
		msg := fmt.Sprintf("S3上合并分片失败！error: %s", err)
		response.FailWithMessage(msg, c)
		return
	}

	// 定义切片文件存储目录和合并后文件存储目录
	targetDir := global.TD27_CONFIG.System.LocalStoreDir + "file/"
	chunkDir := targetDir + mergeChunk.FileHash + "/"
	mergedDir := global.TD27_CONFIG.System.LocalStoreDir + "file/"

	// 获取切片文件列表
	chunkNames, err := getUploadedList(mergeChunk.FileHash, targetDir)
	if err != nil {
		msg := fmt.Sprintf("获取切片列表失败，error: %s", err)
		global.TD27_LOG.Error(msg)
		response.FailWithMessage(msg, c)
		return
	}

	// 创建目标合并文件
	filename := mergeChunk.FileHash + "_" + mergeChunk.FileName
	mergedFilePath := filepath.Join(mergedDir, filename)
	mergedFile, err := os.Create(mergedFilePath)
	if err != nil {
		msg := fmt.Sprintf("Failed to create merged file！error: %s", err)
		response.FailWithMessage(msg, c)
		return
	}
	defer mergedFile.Close()

	// 按照顺序合并块文件为完整文件
	for _, name := range chunkNames {
		chunkFilePath := filepath.Join(chunkDir, name)
		chunkFile, err := os.Open(chunkFilePath)
		if err != nil {
			msg := fmt.Sprintf("打开块文件：%s失败.", name)
			response.FailWithMessage(msg, c)
			return
		}

		_, err = io.Copy(mergedFile, chunkFile)
		if err != nil {
			msg := fmt.Sprintf("合并块文件：%s失败.", name)
			response.FailWithMessage(msg, c)
			return
		}

		err = chunkFile.Close()
		if err != nil {
			return // 如果出现错误，不进行删除操作
		} // 关闭句柄
		// 删除已合并的切片文件
		err = os.Remove(chunkFilePath)
		if err != nil {
			global.TD27_LOG.Warn(fmt.Sprintf("删除切片文件失败,error: %s", err))
			return
		}
	}

	// 确认合并后的文件和上传的文件MD5值是否一致
	md5Str := utils.GetFileMD5(mergedFilePath)
	os.Remove(chunkDir)
	if md5Str == mergeChunk.FileHash {
		response.OkWithMessage("文件上传成功！", c)
		return
	} else {
		msg := fmt.Sprintf("文件合并后的md5值不一致，合并后的md5值为: %s", md5Str)
		response.FailWithMessage(msg, c)
	}
}
